package com.wiscamp.ninechapters.problems.infrastructure.repositories;

import com.wiscamp.ninechapters.problems.domain.models.exams.Exam;
import com.wiscamp.ninechapters.problems.domain.models.exams.ExamTypeRelation;
import com.wiscamp.ninechapters.problems.domain.models.exams.ScoringRule;
import com.wiscamp.ninechapters.problems.domain.repositories.ExamRepository;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ExamConverter;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ExamTypeRelationConverter;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ScoringRuleConverter;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.exams.*;
import com.wiscamp.ninechapters.problems.infrastructure.po.exams.ExamPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.exams.ExamTypeRelationPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.exams.ExamView;
import com.wiscamp.ninechapters.problems.infrastructure.po.exams.ScoringRulePO;
import cube.ddd.aspect.HandleDataException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.domain.BaseRepository;
import cube.ddd.utils.PersistenceUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Objects;

@Repository
public class ExamRepositoryImpl extends BaseRepository implements ExamRepository {
    private final ExamMapper examMapper;

    private final ExamTypeRelationMapper examTypeRelationMapper;

    private final ScoringRuleMapper scoringRuleMapper;

    private final ExamViewMapper examViewMapper;

    private final ScoringRuleViewMapper scoringRuleViewMapper;

    private final ExamTypeRelationViewMapper examTypeRelationViewMapper;

    public ExamRepositoryImpl(ExamMapper examMapper, ExamTypeRelationMapper examTypeRelationMapper, ScoringRuleMapper scoringRuleMapper, ExamViewMapper examViewMapper, ScoringRuleViewMapper scoringRuleViewMapper, ExamTypeRelationViewMapper examTypeRelationViewMapper) {
        this.examMapper = examMapper;
        this.examTypeRelationMapper = examTypeRelationMapper;
        this.scoringRuleMapper = scoringRuleMapper;
        this.examViewMapper = examViewMapper;
        this.scoringRuleViewMapper = scoringRuleViewMapper;
        this.examTypeRelationViewMapper = examTypeRelationViewMapper;
    }

    //region Exam

    private ExamPO getExamPO(long id) {
        return PersistenceUtils.findById(id, examMapper::findById);
    }

    private void loadExamDetails(Exam exam) {
        if (Objects.nonNull(exam)) {
            var typeRelations = getTypeRelationsByExam(exam.getExamId());
            exam.setExamTypeRelations(typeRelations);

            var scoringRules = getScoringRulesByExam(exam.getExamId());
            exam.setScoringRules(scoringRules);
        }
    }

    public Exam getExam(@Min(0) long id) {
        var exam = PersistenceUtils.findEntityById(id, examViewMapper::findById,
                ExamConverter.Instance::toExamByView);
        loadExamDetails(exam);
        return exam;
    }

    public Exam getExamByCode(@NotBlank String examCode, @Min(0) long organizationId) {
        var exam = PersistenceUtils.getEntityByTextAndId(examCode, organizationId, examViewMapper::findByExamCode,
                ExamConverter.Instance::toExamByView);
        loadExamDetails(exam);
        return exam;
    }


    public Collection<Exam> getExamsByName(@NotBlank String examName, @Min(0) long organizationId) {
        var exams = PersistenceUtils.getEntitiesByTextAndId(examName, organizationId, examViewMapper::findListByExamName,
                ExamConverter.Instance::toExamByView);
        for (var exam : exams) {
            loadExamDetails(exam);
        }
        return exams;
    }

    @HandleDataException
    public Collection<Exam> getExamsByOrganization(@Min(0) long organizationId) {
        var exams = PersistenceUtils.getEntitiesById(organizationId, examViewMapper::findListByOrganizationId,
                ExamConverter.Instance::toExamByView);
        for (var exam : exams) {
            loadExamDetails(exam);
        }
        return exams;
    }

    @HandleDataException
    public PagedObjects<Exam> getExamsByQuery(PagedQuery query) {
        PagedObjects<ExamView> pagedObjects = PersistenceUtils.getPersistenceObjectsByQuery(query, examViewMapper::findAll);
        return PersistenceUtils.convertPagedObjects(pagedObjects, ExamConverter.Instance::toExamByView);
    }

    @HandleDataException
    public Exam saveExam(@NotNull Exam exam) {
        var newId = exam.getExamId();
        if (exam.getExamId() <= 0) {
            var newExam = PersistenceUtils.createEntity(exam, examMapper::saveAndFlush,
                    ExamConverter.Instance::toExamPO, ExamConverter.Instance::toExam);
            newId = newExam.getExamId();
        } else {
            var existedPO = getExamPO(exam.getExamId());
            if (existedPO == null) existedPO = new ExamPO();
            PersistenceUtils.updateEntity(exam, existedPO, examMapper::saveAndFlush,
                    ExamConverter.Instance::toExamPO, ExamConverter.Instance::toExam);
        }

        return getExam(newId);
    }

    //endregion

    //region ExamTypeRelation
    private ExamTypeRelationPO getExamTypeRelationPO(long id) {
        return PersistenceUtils.findById(id, examTypeRelationMapper::findById);
    }

    public ExamTypeRelation getExamTypeRelation(long id) {
        return PersistenceUtils.findEntityById(id, examTypeRelationViewMapper::findById,
                ExamTypeRelationConverter.Instance::toExamTypeRelationByView);
    }

    /**
     * Gets a relation of an exam and a problem type
     *
     * @param examId
     * @return
     */
    public Collection<ExamTypeRelation> getTypeRelationsByExam(@Min(0) long examId) {
        return PersistenceUtils.getEntitiesById(examId, examTypeRelationViewMapper::findByExam,
                ExamTypeRelationConverter.Instance::toExamTypeRelationByView);
    }

    /**
     * Creates a relation of an exam and a problem type
     *
     * @param relation
     * @return
     */
    public ExamTypeRelation saveExamTypeRelation(@NotNull ExamTypeRelation relation) {
        var newId = relation.getId();
        if (relation.getId() <= 0) {
            var newRelation = PersistenceUtils.createEntity(relation, examTypeRelationMapper::saveAndFlush,
                    ExamTypeRelationConverter.Instance::toExamTypeRelationPO,
                    ExamTypeRelationConverter.Instance::toExamTypeRelation);
            newId = newRelation.getId();
        } else {
            var existedPO = getExamTypeRelationPO(relation.getId());
            if (existedPO == null) existedPO = new ExamTypeRelationPO();
            PersistenceUtils.updateEntity(relation, existedPO, examTypeRelationMapper::saveAndFlush,
                    ExamTypeRelationConverter.Instance::toExamTypeRelationPO,
                    ExamTypeRelationConverter.Instance::toExamTypeRelation);
        }

        return getExamTypeRelation(newId);
    }

    /**
     * Deletes a relation of an exam and a problem type
     *
     * @param id
     * @return
     */
    @HandleDataException
    public boolean deleteExamTypeRelation(@Min(0) long id) {
        return PersistenceUtils.delete(id, examTypeRelationMapper::deleteById);
    }

    //endregion


    // region scoring rule
    private ScoringRulePO getScoringRulePO(long id) {
        return PersistenceUtils.findById(id, scoringRuleMapper::findById);
    }

    public ScoringRule getScoringRule(long id) {
        return PersistenceUtils.findEntityById(id, scoringRuleViewMapper::findById,
                ScoringRuleConverter.Instance::toScoringRuleByView);
    }

    @HandleDataException
    public ScoringRule getScoringRule(@Min(0) long examId, @Min(0) long problemTypeId, @Min(0) long questionTypeId) {
        var scoringRule = scoringRuleViewMapper.findByExamAndType(examId, problemTypeId, questionTypeId);
        if (Objects.nonNull(scoringRule))
            return ScoringRuleConverter.Instance.toScoringRuleByView(scoringRule);
        return null;
    }

    @HandleDataException
    public Collection<ScoringRule> getScoringRulesByExam(@Min(0) long examId) {
        return PersistenceUtils.getEntitiesById(examId, scoringRuleViewMapper::findListByExam,
                ScoringRuleConverter.Instance::toScoringRuleByView);
    }

    @HandleDataException
    public ScoringRule saveScoringRule(@NotNull ScoringRule rule) {
        var newId = rule.getId();
        if (rule.getId() <= 0) {
            var newRelation = PersistenceUtils.createEntity(rule, scoringRuleMapper::saveAndFlush,
                    ScoringRuleConverter.Instance::toScoringRulePO,
                    ScoringRuleConverter.Instance::toScoringRule);
            newId = newRelation.getId();
        } else {
            var existedPO = getScoringRulePO(rule.getId());
            if (existedPO == null) existedPO = new ScoringRulePO();
            PersistenceUtils.updateEntity(rule, existedPO, scoringRuleMapper::saveAndFlush,
                    ScoringRuleConverter.Instance::toScoringRulePO,
                    ScoringRuleConverter.Instance::toScoringRule);
        }
        return getScoringRule(newId);
    }

    @HandleDataException
    public boolean deleteScoringRule(@Min(0) long id) {
        return PersistenceUtils.delete(id, scoringRuleMapper::deleteById);
    }
    // endregion
}
